﻿using System;
using System.Linq.Expressions;
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;

namespace MagickServer.Utils {
    public static class ComponentRegistrationExtensions {
        public static IKernel Kernel { private get; set; }

        public static ComponentRegistration<T> FactoryMethod<T, S>(this ComponentRegistration<T> reg, Func<S> factory) where S : T {
            var factoryName = typeof (GenericFactory<S>).FullName;
            Kernel.Register(Component.For<GenericFactory<S>>().Named(factoryName).Instance(new GenericFactory<S>(factory)));
            reg.Configuration(Attrib.ForName("factoryId").Eq(factoryName), Attrib.ForName("factoryCreate").Eq("Create"));
            return reg;
        }

        public static ComponentRegistration<T> FactoryMethod<T, S>(this ComponentRegistration<T> reg, Func<IKernel, S> factory) where S : T {
            var factoryName = typeof (GenericFactoryWithKernel<S>).FullName;
            Kernel.Register(Component.For<GenericFactoryWithKernel<S>>().Named(factoryName).Instance(new GenericFactoryWithKernel<S>(factory, Kernel)));
            reg.Configuration(Attrib.ForName("factoryId").Eq(factoryName), Attrib.ForName("factoryCreate").Eq("Create"));
            return reg;
        }

        public static ComponentRegistration<T> Factory<T, S, U>(this ComponentRegistration<T> reg, Func<S, U> factory) where U : T {
            return reg.FactoryMethod(k => factory.Invoke(k.Resolve<S>()));
        }

        // Supports Resolve with parameters. DOES NOT WORK YET.
        public static ComponentRegistration<TService> Factory<TService, TFactoryService, U>(this ComponentRegistration<TService> reg, Func<TFactoryService, Func<U, TService>> factory) where TFactoryService: class {
            var factoryName = Guid.NewGuid().ToString();
            Kernel.Register(Component.For<GenericFactoryWithParam<TFactoryService>>()
                                .Named(factoryName)
                                .Instance(new GenericFactoryWithParam<TFactoryService>((k, p) => k.Resolve<TFactoryService>(), Kernel)));
            reg
                .Attribute("factoryId").Eq(factoryName)
                .Attribute("factoryCreate").Eq("Create");
            return reg;
        }

        private class GenericFactoryWithKernel<T> {
            private readonly Func<IKernel, T> factoryMethod;
            private readonly IKernel kernel;

            public GenericFactoryWithKernel(Func<IKernel, T> factoryMethod, IKernel kernel) {
                this.factoryMethod = factoryMethod;
                this.kernel = kernel;
            }

            public T Create() {
                return factoryMethod(kernel);
            }
        }

        private class GenericFactoryWithParam<T> {
            private readonly Func<IKernel, object, T> factoryMethod;
            private readonly IKernel kernel;

            public GenericFactoryWithParam(Func<IKernel, object, T> factoryMethod, IKernel kernel) {
                this.factoryMethod = factoryMethod;
                this.kernel = kernel;
            }

            public T Create(object param) {
                return factoryMethod(kernel, param);
            }
        }

        private class GenericFactoryWithGenericParam<T, U> {
            private readonly Func<U, T> factoryMethod;

            public GenericFactoryWithGenericParam(Func<U, T> factoryMethod) {
                this.factoryMethod = factoryMethod;
            }

            public T Create(U param) {
                return factoryMethod(param);
            }
        }

        private class GenericFactory<T> {
            private readonly Func<T> factoryMethod;

            public GenericFactory(Func<T> factoryMethod) {
                this.factoryMethod = factoryMethod;
            }

            public T Create() {
                return factoryMethod();
            }
        }
    }
}